﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using ParserGenerator.GrammarAnalysis;
using ParserGenerator.GrammarConstruction;

namespace ParserGenerator.ParserConstruction
{
    [Serializable]
    public sealed class ParserAutomatonStateTable : ReadOnlyCollection<ParserAutomatonState>
    {
        public ParserAutomaton ParserAutomaton { get; }
        public GrammarGoalDescriptorTable Goals => ParserAutomaton.Goals;
        public GrammarTerminalTable Tokens => ParserAutomaton.Tokens;
        public ParserAutomatonType AutomatonType => ParserAutomaton.AutomatonType;
        public bool UseDefaultReductions => ParserAutomaton.UseDefaultReductions;

        public ParserAutomatonActionsTable Actions { get; }
        public ParserAutomatonGotosTable Gotos { get; }

        private readonly Dictionary<ParserAutomatonItemSet, ParserAutomatonState> _dict;
        private readonly Queue<ParserAutomatonState> _queue;

        internal ParserAutomatonReductionLookaheadSetsTable ReductionLookaheadSets { get; }
        internal ParserAutomatonStatePredecessorsTable Predecessors { get; }
        internal ParserAutomatonItemLookaheadSetsCache ItemLookaheadSetsCache { get; }
        internal LexicalPrecedenceResolutionCache LexicalResolutionCache { get; }
        internal ParserAutomatonAnnotationNodesTable AnnotationNodes { get; }
        internal ParserAutomatonInadequacyNodesTable InadequacyNodes { get; }

        internal ParserAutomatonStateTable(ParserAutomaton parserAutomaton)
            : base(new List<ParserAutomatonState>())
        {
            if (parserAutomaton == null)
                throw new ArgumentNullException(nameof(parserAutomaton));
            ParserAutomaton = parserAutomaton;
            _dict = new Dictionary<ParserAutomatonItemSet, ParserAutomatonState>();
            _queue = new Queue<ParserAutomatonState>();
            ConstructLR0States();
            _dict = null;
            _queue = null;
            ReductionLookaheadSets = new ParserAutomatonReductionLookaheadSetsTable(this);
            bool lookaheadsOnly = (AutomatonType == ParserAutomatonType.LALR);
            Gotos = new ParserAutomatonGotosTable(this, lookaheadsOnly);
            if (AutomatonType != ParserAutomatonType.LALR)
            {
                Predecessors = new ParserAutomatonStatePredecessorsTable(this);
                ItemLookaheadSetsCache = new ParserAutomatonItemLookaheadSetsCache(Gotos, AutomatonType);
                if (AutomatonType == ParserAutomatonType.PSLR)
                    LexicalResolutionCache = new LexicalPrecedenceResolutionCache(ParserAutomaton.ScannerAutomaton);
                if (AutomatonType != ParserAutomatonType.LR)
                    AnnotationNodes = new ParserAutomatonAnnotationNodesTable(this);
                SplitLalrStates();
                if (AutomatonType == ParserAutomatonType.LR)
                    ReductionLookaheadSets = ItemLookaheadSetsCache.SaveIelrReductionLookaheadSets(this);
                else
                {
                    ReductionLookaheadSets = new ParserAutomatonReductionLookaheadSetsTable(this);
                    Gotos = new ParserAutomatonGotosTable(this, true);
                }
                Predecessors = null;
                ItemLookaheadSetsCache = null;
                AnnotationNodes = null;
            }
            Actions = new ParserAutomatonActionsTable(this);
            Predecessors = new ParserAutomatonStatePredecessorsTable(this);
            InadequacyNodes = new ParserAutomatonInadequacyNodesTable(Actions);
        }

        private ParserAutomatonState GetStateOrCreate(GrammarSymbol symbol, ParserAutomatonItemSet items)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            ParserAutomatonState state;
            if (!_dict.TryGetValue(items, out state))
            {
                state = new ParserAutomatonState(this, symbol, items);
                Items.Add(state);
                _dict.Add(items, state);
                _queue.Enqueue(state);
            }
            return state;
        }

        internal ParserAutomatonState CreateNewIsocore(ParserAutomatonState lr0Isocore, ParserAutomatonItemLookaheadSets sets)
        {
            if (lr0Isocore == null)
                throw new ArgumentNullException(nameof(lr0Isocore));
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            ParserAutomatonState state = new ParserAutomatonState(lr0Isocore);
            Items.Add(state);
            if (sets.CheckHaveLookheads())
                ItemLookaheadSetsCache.AddIelrLookaheadSets(state, sets);
            return state;
        }

        private void ConstructLR0States()
        {
            foreach (GrammarGoalDescriptor goal in Goals.Values)
            {
                ParserAutomatonItemSet items = new ParserAutomatonItemSet();
                items.Add(goal.AcceptProduction.FirstItem);
                goal.State = GetStateOrCreate(goal.StartSymbol, items);
            }
            while (_queue.Count > 0)
            {
                ParserAutomatonState source = _queue.Dequeue();
                foreach (var t in source.Partition())
                {
                    ParserAutomatonState target = GetStateOrCreate(t.Key, t.Value);
                    source.AddTransition(t.Key, target);
                }
                source.CheckConsistency(UseDefaultReductions);
            }
        }

        private void SplitLalrStates()
        {
            foreach (GrammarGoalDescriptor goal in Goals.Values)
            {
                var sets = new ParserAutomatonItemLookaheadSets(goal.State);
                var set = new ParserAutomatonItemLookaheadSet(goal.State, goal.AcceptProduction.FirstItem);
                set.UnionWith(goal.EndMarkers);
                ItemLookaheadSetsCache.AddIelrLookaheadSets(goal.State, sets);
            }
            for (int i = 0; i < Items.Count; i++)
            {
                ParserAutomatonState isocore = Items[i];
                // Copy transition keys so that transition dictionary can be updated.
                GrammarSymbol[] symbols = isocore.Transitions.Keys.ToArray();
                foreach (GrammarSymbol symbol in symbols)
                {
                    ParserAutomatonState target = isocore.Transitions[symbol];
                    var lookaheadFilter = target.ComputeLookaheadFilter();
                    var lookaheadSets = ItemLookaheadSetsCache.ComputeIelrLookaheadSets(isocore, target, lookaheadFilter);
                    ParserAutomatonState t = target.ComputeIelrState(lookaheadSets);
                    isocore.UpdateTransition(symbol, t);
                }
            }
        }
    }
}
